<?php
/**
 * Профиль пользователя.
 */
class Users_Helper_Profile extends Zend_Controller_Action_Helper_Abstract {
    const FORM_CONFIG_PROFILE = 'forms/profile';

    /**
     * @var Zend_Form
     */
    protected static $_oProfileForm;

    /**
     * @var Users_Model_User
     */
    protected static $_oProfileUser;

    /**
     * @var boolean
     */
    protected static $_bReadOnly = false;

    /**
     * @param boolean|null $value
     * @return bool
     */
    public function readOnly($value = null) {
        $readOnly = self::$_bReadOnly;
        if ($value !== null) {
            self::$_bReadOnly = (boolean)$value;
        }
        return $readOnly;
    }

    /**
     * Изменение или удаление объекта формы пользовательского профиля.
     *
     * @param Zend_Form|null $objectOrNull
     * @return void
     */
    public function setProfileForm($objectOrNull) {
        if ($objectOrNull === null || $objectOrNull instanceof Zend_Form) {
            self::$_oProfileForm = $objectOrNull;
        }
    }

    /**
     * Перенаправление пользователя по обратному УРЛ.
     *
     * @return void
     */
    public function redirectToBackUrl() {
        /** @var Zend_Controller_Request_Http $request */
        $request = $this->getActionController()->getRequest();

        if ($request->getParam('backurl')) {
            /** @var Workset_Controller_Action_Helper_HttpUri $helper */
            $helper = Zend_Controller_Action_HelperBroker::getStaticHelper('httpUri');
            $sBackUrl = $helper->getUri($request->getParam('backurl'));
        }
        else {
            $sBackUrl = strpos($_SERVER['HTTP_REFERER'],parse_url('http://'.$request->getHttpHost().$_SERVER['REQUEST_URI'],PHP_URL_PATH)) >= 0
                ? 'http://'.$request->getHttpHost().'/'
                : $_SERVER['HTTP_REFERER'];
        }

        /** @var Zend_Controller_Response_Http $response  */
        $response = $this->getActionController()->getResponse();
        $response->setRedirect($sBackUrl,302);
    }

    /**
     * Проведение операции XOR для двух хешей.
     *
     * @param string $sHash1
     * @param string $sHash2
     * @return string
     */
    public function xorHash($sHash1,$sHash2) {
        $sHash = '';
        $aHex = array(
            0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f',
            'a' => 10, 'b' => 11, 'c' => 12, 'd' => 13, 'e' => 14, 'f' => 15,
            'A' => 10, 'B' => 11, 'C' => 12, 'D' => 13, 'E' => 14, 'F' => 15,
        );

        for ($i=0, $iLen=min(strlen($sHash1),strlen($sHash2)); $i<$iLen; $i++) {
            if (isset($aHex[$sHash1[$i]]) && isset($aHex[$sHash2[$i]])) {
                $sHash.= (string)$aHex[$aHex[$sHash1[$i]] ^ $aHex[$sHash2[$i]]];
            }
            else {
                $sHash.= '?';
            }
        }

        return $sHash;
    }

    /**
     * Инициализация объектов профиля пользователя.
     */
    public function initProfileObjects() {
        $request = $this->getActionController()->getRequest();
        // Создание объекта пользователя.
        if (self::$_oProfileUser === null) {
            try {
                if (0 < (int)$request->getParam('id',0)) {
                    self::$_oProfileUser = Users_Model_User::getById($request->getParam('id'));
                }
                elseif ($request->getParam('login')) {
                    self::$_oProfileUser = Users_Model_User::getByLogin($request->getParam('login'));
                }
                elseif (!Users_Model_User::getUser()->isGuest()) {
					self::$_oProfileUser = Users_Model_User::getUser();
				}
				else {
                    self::$_oProfileUser = Users_Model_User::getById(0);
                }
            }
            catch (Exception $oException) {
                throw new Zend_Exception($oException->getMessage(),404);
            }
        }

        if (self::$_oProfileForm === null) {
            // Создание объекта формы.
            /** @var Workset_Controller_Action_Helper_ModuleConfig $helper */
            $helper = Zend_Controller_Action_HelperBroker::getStaticHelper('moduleConfig');
            self::$_oProfileForm = new Users_Form_Login($helper->getConfig(self::FORM_CONFIG_PROFILE));

            // Конфигурация валидаторов для поля "Логин пользователя".
            /** @var Workset_Validate_List $oVList */
            $oVList = self::$_oProfileForm->getElement('user_login')->getValidator('List');
            /** @var Zend_Validate_Identical $oValidator */
			$oValidator = $oVList->getValidator('Zend_Validate_Identical');
			$oValidator and $oValidator->setToken(self::$_oProfileUser->getLogin());

            // Конфигурация валидаторов для поля "Адрес эл.почты пользователя".
            $oVList = self::$_oProfileForm->getElement('user_email')->getValidator('List');
            $oValidator = $oVList->getValidator('Zend_Validate_Identical');
			$oValidator and $oValidator->setToken(self::$_oProfileUser->getEMail());

            // Если пользователь - администратор, то ему достаточно знать собственный пароль для изменения любого профиля.
            $sCurrentLogin = Users_Model_User::getUser()->isAdmin()
                ? Users_Model_User::getUser()->getLogin()
                : self::$_oProfileUser->getLogin();
            $sPasswordHash = Users_Model_User::getUser()->isAdmin()
                ? Users_Model_User::getUser()->getPasswordHash()
                : self::$_oProfileUser->getPasswordHash();

            // Конфигурация валидаторов для поля "Запрос пароля для изменения данных".
            $oElement = self::$_oProfileForm->getElement('user_password');
            $oElement->addFilter('PasswordHash',array('login' => $sCurrentLogin));
            $oValidator = $oElement->getValidator('Identical');
            $oValidator->setToken($sPasswordHash);
            $oValidator->setMessage('Wrong password for this user',Zend_Validate_Identical::NOT_SAME);

            // Конфигурация валидаторов "задание нового пароля".
            $oElement = self::$_oProfileForm->getElement('user_new_password_confirm');
            $oElement->setRequired(trim($request->getParam('user_new_password')) != '');

            if ($oElement->isRequired()) {
                $oFilter = new Workset_Filter_PasswordHash(self::$_oProfileUser->getLogin());
                self::$_oProfileForm->getElement('user_new_password')->addFilter($oFilter);

                $oValidator = $oElement->getValidator('Identical');
                $oValidator->setToken(trim($request->getParam('user_new_password')));
                $oValidator->setMessage('Wrong confirmation of new password',Zend_Validate_Identical::NOT_SAME);
            }
        }
    }

    /**
     * Получение формы профиля пользователя.
     *
     * @param array|null $aOnlyFields
     * @param boolean $bOriginal
     * @return Zend_Form
     */
    public function getProfileForm($aOnlyFields = null, $bOriginal = false) {
        // Инициализация объекта формы.
        if (self::$_oProfileForm === null) {
            $this->initProfileObjects();
        }

        // Настраиваем объект формы.
        $oForm = $bOriginal ? self::$_oProfileForm : clone self::$_oProfileForm;

        /** @var Zend_Form_Element $oElement */
        foreach ($oForm->getElements() as $sName => $oElement) {
            self::$_bReadOnly && $oElement->setAttrib('disabled','disabled');

            if (is_array($aOnlyFields) && array_search($sName, $aOnlyFields) === false) {
                $oForm->removeElement($sName);
            }
        }

        return $oForm;
    }

    /**
     * Получение объекта пользователя.
     *
     * @return Users_Model_User
     */
    public function getProfileUser() {
        // Инициализация объекта пользователя.
        if (self::$_oProfileUser === null) {
            $this->initProfileObjects();
        }

        return self::$_oProfileUser;
    }

    /**
     * Валидация формы профиля.
     *
     * @param Zend_Form $oForm
     * @return boolean
     */
    public function validProfileForm(Zend_Form $oForm) {
        /** @var Zend_Controller_Request_Http $request  */
        $request = $this->getActionController()->getRequest();
        return $oForm->isValid($request->getPost());
    }

    /**
     * Обработка единственного поля.
     *
     * @param string $sFieldName
     * @param string $sUserMethod
     * @param boolean $bNotSave
     */
    public function profileFieldExecute($sFieldName, $sUserMethod, $bNotSave = false, $aCheckFields = array()) {
        $oForm = $this->getProfileForm(array_unique(array_merge(array($sFieldName), $aCheckFields)));
        $oUser = $this->getProfileUser();

        /** @var Zend_Controller_Request_Http $request */
        $request = $this->getActionController()->getRequest();

        // В случае наличия POST данных, начинаем их обработку.
        if ($request->isPost()) {
            if ($this->validProfileForm($oForm)) {
                $mValue = $oForm->getValue($sFieldName);
                call_user_func(array($oUser,'set'.$sUserMethod),$mValue);

                if ($bNotSave == false) {
                    $oUser->save();
                }
            }
        }
		// Заполнение формы данными из БД.
        else {
            $mValue = call_user_func(array($oUser, 'get'.$sUserMethod));
            $oElement = $oForm->getElement($sFieldName);

            if ($oElement instanceof Zend_Form_Element_Checkbox) {
                /** @var Zend_Form_Element_Checkbox $oElement */
                $oElement->setChecked($mValue);
            }
            else {
                $oElement->setValue($mValue);
            }

            self::$_oProfileForm->getElement($sFieldName)->setValue($mValue);
        }

        // Запоминаем объекты для вывода в шаблоне.
        /** @noinspection PhpUndefinedFieldInspection */
        $this->view->form = $oForm;
    }

    /**
     * Отсылка письма для подтверждения изменений поля.
     *
     * @param string $sText Короткий текст письма
     * @param string $sField Название изменяемого поля
     * @param string $sOldValue Старое значение поля
     * @param string $sNewValue Новое значение поля
     * @param string $sUri Адрес обработчика подтверждения изменения значения поля
     */
    public function sendConfirmEmail($sText,$sField,$sOldValue,$sNewValue,$sUri) {
        // Получение параметров пользователя.
        $iId = $this->getProfileUser()->getId();
        $sLogin = $this->getProfileUser()->getLogin();
        $sEmail = $this->getProfileUser()->getEMail();
        $sSecretWord = $this->getProfileUser()->getSecretWord();

        /** @var Zend_Controller_Request_Http $request */
        $request = $this->getActionController()->getRequest();

        // Формирование URL.
        /** @var Workset_Controller_Action_Helper_HttpUri $helper */
        $helper = Zend_Controller_Action_HelperBroker::getStaticHelper('httpUri');
        $oUri = $helper->getUri($sUri);
        $oUri->setQuery(array(
            'id' => $iId,
            'field' => $sField,
            'value' => $sNewValue,
            'backurl' => (string)$helper->getUri($request->getRequestUri()),
            'signature' => md5(join('::',array($sSecretWord,$iId,$sField,$sOldValue,$sNewValue))),
        ));

        // Формирование HTML тела письма.
        $sHtml = '<h3> '.$sText." </h3>\n<br/>\n"
            .'Старое значение: <b> '.$sOldValue." </b>\n<br/>"
            .'Новое значение:  <b> '.preg_replace('~^.*?[:]~','',$sNewValue)." </b>\n<br/>"
            ."\n"
            ."Ссылка для подтверждения: <a href=\"$oUri\">$oUri</a>";

        // Отправляем письмо со ссылкой для активации.
        $oMail = new Zend_Mail('utf-8');
        $oMail->setHeaderEncoding(Zend_Mime::ENCODING_BASE64);
        $oMail->addTo($sEmail,$sLogin);
        $oMail->setSubject($sText.' ('.$request->getHttpHost().')');
        $oMail->setBodyHtml($sHtml,'utf-8',Zend_Mime::ENCODING_QUOTEDPRINTABLE);
        $oMail->send();
    }

    /**
     * Проверка корректности запроса подтверждения изменений поля профиля.
     *
     * @param string $sFieldName
     * @param string $sOldValue
     * @param boolean $bNoException
     */
    public function checkConfirmRequest($sFieldName, $sOldValue, $bNoException = false) {
        // Получение параметров пользователя.
        $iId = $this->getProfileUser()->getId();
        $sSecretWord = $this->getProfileUser()->getSecretWord();

        // Получение параметров запроса.
        $aParams = $this->getActionController()->getRequest()->getParams();
        $sField = $aParams['field'];
        $sNewValue = $aParams['value'];
        $sSignature = md5(join('::',array($sSecretWord,$iId,$sField,$sOldValue,$sNewValue)));

        // Сверка подписей и названия поля.
        if ($sSignature != $aParams['signature'] || $sField != $sFieldName) {
            if ($bNoException) {
                return false;
            }

            throw new Zend_Exception('Wrong confirm request!',403);
        }

        return true;
    }

    /**
     * Изменение логина пользователя.
     *
     * Т.к. от логина пользователя зависит хранимый хэш пароля, то вычисляем но-
     * вый хэш, кодируем его и передаём в ссылке подтверждения вместе с логином.
     *
     * @param boolean $bNotSave Флаг отмены сохранения изменений полей объекта.
     * @param boolean $bNotSendEmail Флаг отмены подтверждения через e-mail.
     */
    public function profileLoginAction($bNotSave = false, $bNotSendEmail = false) {
        $sLogin = $this->getProfileUser()->getLogin();
        $this->profileFieldExecute('user_login','Login',true,array('user_password'));

        /** @var Zend_Controller_Request_Http $request */
        $request = $this->getActionController()->getRequest();

        // Если изменён логин...
        if ($sLogin != $sNewLogin = $this->getProfileUser()->getLogin()) {
            $sPassword = $request->getParam('user_password');
            $sHash = Workset_Auth::getInstance()->getPasswordHash($sNewLogin,$sPassword);
            $sHash = $this->xorHash($sHash,md5($this->getProfileUser()->getSecretWord()));

            // Вызываем метод сохранения нового логина.
            if ($bNotSendEmail) {
                $request->setParam('value',"$sHash:$sNewLogin");
                $this->profileLoginConfirmAction($bNotSave);
            }
            else { // Отправляем письмо с запросом подтверждения смены логина.
                $this->getProfileUser()->setLogin($sLogin);
                $this->sendConfirmEmail('Подтверждение изменения логина пользователя','login',$sLogin,"$sHash:$sNewLogin",'/users/profile/login-confirm');
            }
        }
    }

    /**
     * Применение подтверждённых изменений логина пользователя.
     *
     * Т.к. от логина пользователя зависит хранимый хэш пароля, то извлекаем его
     * из ссылки подтверждения, после чего декодируем и сохраняем.
     *
     * @param boolean $bNotSave Флаг отмены сохранения изменений полей объекта.
     *
     * @TODO Перезапоминание пользователя
     */
    public function profileLoginConfirmAction($bNotSave = false) {
        /** @var Zend_Controller_Request_Http $request */
        $request = $this->getActionController()->getRequest();

        $oUser = $this->getProfileUser();

        $bNotSave || $this->checkConfirmRequest('login',$oUser->getLogin());
        list($sHash,$sLogin) = explode(':',$request->getParam('value'),2);

        $oUser->setLogin($sLogin);
        $oUser->setPasswordHash($this->xorHash($sHash,md5($oUser->getSecretWord())));

        if ($bNotSave == false) {
            $oUser->save();
            /** @noinspection PhpUndefinedFieldInspection */
            $this->_helper->viewRenderer->setNoRender(true);
            $this->redirectToBackUrl();
        }
    }

    /**
     * Изменение фамилии пользователя.
     */
    public function profileLastNameAction($bNotSave = false) {
        $this->profileFieldExecute('user_last_name','LastName',$bNotSave);
    }

    /**
     * Изменение имени пользователя.
     */
    public function profileFirstNameAction($bNotSave = false) {
        $this->profileFieldExecute('user_first_name','FirstName',$bNotSave);
    }

    /**
     * Изменение отчества пользователя.
     */
    public function profilePatronymicAction($bNotSave = false) {
        $this->profileFieldExecute('user_patronymic','Patronymic',$bNotSave);
    }

    /**
     * Изменение электронного адреса пользователя.
     *
     * @param boolean $bNotSave Флаг отмены сохранения изменений полей объекта.
     * @param boolean $bNotSendEmail Флаг отмены подтверждения через e-mail.
     */
    public function profileEmailAction($bNotSave = false, $bNotSendEmail = false) {
        $sEMail = $this->getProfileUser()->getEMail();
        $this->profileFieldExecute('user_email','EMail',true,array('user_password'));

        // Если изменён адрес e-mail...
        if ($sEMail != $sNewEMail = $this->getProfileUser()->getEMail()) {
            // Вызываем метод сохранения нового адреса электронного ящика.
            if ($bNotSendEmail) {
                $this->getActionController()->getRequest()->setParam('value',$sNewEMail);
                $this->profileEmailConfirmAction($bNotSave);
            }
            else { // Отправляем письмо с запросом подтверждения смены адреса электронного ящика.
                $this->sendConfirmEmail('Подтверждение изменения адреса ел.почты пользователя','email',$sEMail,$sNewEMail,'/users/profile/email-confirm');
                $this->getProfileUser()->setEMail($sEMail);
            }
        }
    }

    /**
     * Применение подтверждённых изменений адреса эл.почты пользователя.
     *
     * @param boolean $bNotSave Флаг отмены сохранения изменений полей объекта.
     */
    public function profileEmailConfirmAction($bNotSave = false) {
        $oUser = $this->getProfileUser();

        $bNotSave || $this->checkConfirmRequest('email',$oUser->getEMail());

        $oUser->setEMail($this->getActionController()->getRequest()->getParam('value'));

        if ($bNotSave == false) {
            $oUser->save();
            /** @var Zend_Controller_Action_Helper_ViewRenderer $helper */
            $helper = Zend_Controller_Action_HelperBroker::getStaticHelper('viewRender');
            $helper->setNoRender(true);
            $this->redirectToBackUrl();
        }
    }

    /**
     * Применение изменения флага отправки внутренних сообщений на e-mail.
     *
     * @param boolean $bNotSave Флаг отмены сохранения изменений полей объекта.
     */
    public function profileEmailMessageAction($bNotSave) {
        $this->profileFieldExecute('message_by_email','MessageByEMail',$bNotSave);
    }

    /**
     * Задание нового пароля пользователя.
     *
     * @param boolean $bNotSave
     */
    public function profileNewPasswordAction($bNotSave = false) {
        if ($this->getActionController()->getRequest()->getParam('user_new_password',false)) {
            $this->profileFieldExecute('user_new_password','PasswordHash',$bNotSave,array('user_password','user_new_password_confirm'));
            // Необходимо работать с оригиналом.
            self::$_oProfileForm->getElement('user_new_password')->setValue('');
        }
    }
}
?>